<script setup lang="ts">
import {
  VNode,
  computed,
  defineComponent,
  h,
  isVNode,
  mergeProps,
  ref,
  unref,
  useSlots,
  withKeys,
} from "vue";

import {
  isNumber,
  isString,
  isFunction,
  uniqueId,
  castArray,
  entries,
  fromPairs,
  upperFirst,
} from "lodash-es";

import FormField, { IFormFieldProps } from "./ProFormField.ts";

import { TFormItemChildren, TProFormProps } from "../type";
import { ElIcon, ElTooltip } from "element-plus";
import { Warning } from "@element-plus/icons-vue";

const emits = defineEmits<{
  (e: "reset"): void;
  (e: "submit"): void;
}>();

const props = withDefaults(defineProps<TProFormProps>(), {
  footer: () => ({}),
  confirmText: "确定",
  resetText: "重置",
});

const colProps = computed(() => handleColProps(props.col || 8));

// 设置唯一id
// 获取可渲染列
const renderColumns = computed(() => {
  // 设置sid
  props.formItems?.forEach(
    // @ts-ignore
    (e) => (Reflect.set(e, "__sid", e.__sid || uniqueId()), e)
  );

  return props.formItems.filter(({ hidden }) => {
    return !(isFunction(hidden)
      ? hidden(props.model || {})
      : unref(hidden) ?? false);
  });
});

// 合并Col
const handleColProps = (val: any, defVal = null, replace = false) => {
  if (val) {
    val = isNumber(val) ? { span: val } : val;
    return replace || !defVal ? val : mergeProps(defVal, val);
  }
  return defVal;
};

// 对外暴露表单方法
const refForm = ref();
const expose: any = {};
[
  "clearValidate",
  "resetFields",
  "scrollToField",
  "validate",
  "validateField",
].forEach((key) => {
  expose[key] = (...arg: any[]) => refForm.value?.[key](...arg);
});

defineExpose(expose);

// FormItem label 内容
const getFormLabel = (label: any = "", model: any, labelSuffix: string = "") =>
  (isFunction(label) ? label(model) : label) + labelSuffix;

// 处理提示
const getTooltipProps = (tooltip: any) =>
  tooltip && isString(tooltip) ? { content: tooltip } : tooltip;

// ----> S 处理ProTableMax 来的 slots

const slots = useSlots();
const slotMap = computed<any>(() => {
  // @ts-ignore
  const res = getScope(slots.default?.()[0]["children"]);
  return res?.children;
});

// 递归找出最近的ProFormScope
const getScope = (nodes: any) => {
  if (nodes) {
    const scope = nodes[0];
    const scopeName = scope?.type?.name || scope?.type || scope?.type;

    if (isVNode(scope) && scopeName == "ProFormScope") {
      return scope;
    }

    return getScope(scope?.children);
  }
};
// <---- E

const submitFn = () => emits("submit");

function compileSlotsProps(
  slots?: VNode | Record<string, IFormFieldProps | VNode>
) {
  if (!slots || isVNode(slots)) return slots;
  return fromPairs(
    entries(slots as object).map(([key, value]) => [
      key,
      () =>
        isVNode(value) ? value : h(FormField, { model: props.model, ...value }),
    ])
  );
}

const childProps = (schema: TFormItemChildren) => {
  // 包装 schema 自动提交事件
  const submitEvents = schema?.submitEvent
    ? castArray(schema.submitEvent)
    : null;

  const eventProps = submitEvents?.reduce((events, item) => {
    const [key, ...modifys] = item.split(".");
    Reflect.set(
      events,
      `on${upperFirst(key)}`,
      key == "keyup" ? withKeys(submitFn, modifys) : submitFn
    );

    return events;
  }, Object());

  const result = {
    ...schema,
    slots: compileSlotsProps(schema?.slots),
    props: mergeProps(eventProps, schema?.props || {}),
  };

  return result;
};

const slotEvents = {
  submit: () => emits("submit"),
  reset: () => emits("reset"),
};

// 自定义FormItem 的label
const CompFormLabel = defineComponent(
  (props) => {
    const Child = h("div", { class: "flex items-center" }, [
      h(
        ElIcon,
        {
          size: props.iconSize || 18,
          color: props.iconColor,
        },
        h(props.icon || Warning)
      ),
      props.label,
    ]);

    const Wrap = props.tooltip
      ? h(ElTooltip, {
          placement: "top-start",
          ...getTooltipProps(props.tooltip),
        })
      : h("div");

    return () => h(Wrap, null, Child);
  },
  {
    props: ["label", "tooltip", "icon", "iconColor", "iconSize"],
  }
);
</script>

<template>
  <el-form ref="refForm" :model="model" :labelSuffix="labelSuffix">
    <!-- S 用于避免单个input导致页面刷新 -->
    <input hidden />
    <!-- E  -->

    <el-row :gutter="gutter ?? 16">
      <template v-for="item in renderColumns" :key="item.__sid">
        <el-col v-bind="handleColProps(item.col, colProps, item.colReplace)">
          <el-form-item
            v-bind="item.itemProps"
            :label="getFormLabel(item.label, model)"
            :prop="item.prop || castArray(item.children)[0]?.field"
          >
            <template #label v-if="item.tooltip || item.icon">
              <CompFormLabel
                :label="getFormLabel(item.label, model, labelSuffix)"
                :icon="item.icon"
                :iconColor="item.iconColor"
                :iconSize="item.iconSize"
                :tooltip="item.tooltip"
              />
            </template>

            <component
              v-if="item.slot && slotMap?.[item.slot]"
              :is="slotMap[item.slot]"
              :search="model"
              :children="item.children"
              v-bind="slotEvents"
            />

            <slot
              v-else
              :name="item.slot || '__default'"
              :config="item.children"
              :model="model"
            >
              <template
                v-for="(schema, index) in castArray(item.children)"
                :key="index"
                :span="schema.col"
              >
                <FormField
                  v-bind="childProps(schema)"
                  :model="model"
                  style="width: 100%"
                />
              </template>
            </slot>
          </el-form-item>
        </el-col>
      </template>

      <slot name="form-footer" v-if="footer !== false">
        <component
          v-if="slotMap?.['form-footer']"
          :is="slotMap['form-footer']"
        />

        <el-col
          v-else
          :class="[
            footer.col == 0 && 'flex-auto text-left',
            { 'text-right': align == 'right', 'text-left': align == 'left' },
          ]"
          v-bind="handleColProps(footer.col || 24)"
        >
          <el-form-item>
            <div class="w-full">
              <el-button
                v-if="resetText"
                plain
                v-bind="resetProps"
                :disabled="loading"
                @click="slotEvents.reset"
                >{{ resetText }}
              </el-button>

              <el-button
                v-if="confirmText"
                type="primary"
                v-bind="confirmProps"
                :loading="loading"
                @click="slotEvents.submit"
              >
                {{ confirmText }}
              </el-button>
            </div>
          </el-form-item>
        </el-col>
      </slot>
    </el-row>
  </el-form>
</template>
